﻿// Inner Fire 游戏引擎库
// PhysicsBone - 动态服装/骨骼
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层（4）
// 动态服装/骨骼。使用Verlet粒子进行简单的物理骨骼模拟。
//
// TIPS:
// 1、当项目离地图远的时候，取消物理演算。然后重启的时候，需要刷新？
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-06-19

#include "physics_bone.h"
#include <game/animation/animator.h>
#include <game/animation/avatar.h>
#include <game/object/game_object.h>
#include <graphics/imath.h>
#include <klog.h>

namespace ifire::game {

inline IVec coord_to_world(IVec pos, FMat world_mat) {
  return DirectX::XMVector3TransformCoord(pos, world_mat);
}

inline IVec vector_to_world(IVec pos, FMat world_mat) {
  return DirectX::XMVector3TransformNormal(pos, world_mat);
}

PhysicsBone::PhysicsBone(GameObject* game_object)
    : Component(ComponentType(), game_object) {
  // 保存Object的位置
  auto& t = game_object_->GetTransform();
  trans_mat_ = t.mat;
  object_position_ = XMLoadFloat3(&t.position);
  prev_object_position_ = object_position_;

  // PhysicsBone需要从Animator中读取信息
  animator_ = (Animator*)game_object_->GetComponent(Animator::ComponentType());
  animator_->SetPhysicsBone(this);

  armature_ = animator_->GetArmature();
  avatar_ = armature_->GetAvatar();

  // 读取配置
  auto& list = avatar_->PhysicsBones();
  auto size = list.size();
  if (size == 0) {
    KERROR("%s(Avatar: %s) do not have physics bone.",
         game_object->GetName().c_str(), avatar_->name.c_str());
  }

  trees_.reserve(list.size());
  for (auto& e : list) {
    auto item = std::make_unique<PhysicsParticleTree>(*this, *e.setting);
    item->root_id = armature_->GetBoneId(e.bone);
    // 依次加入子项
    FillBones(item.get(), armature_->GetNodeTree()->Find(e.bone), -1, 0);

    trees_.push_back(std::move(item));
  }
}

PhysicsBone::~PhysicsBone() {}

void PhysicsBone::FixedUpdate(float delta_time) {}

void PhysicsBone::LateUpdate(float delta_time) {}

ikit::sstr PhysicsBone::ComponentType() {
  static ikit::sstr TYPE_NAME("PhysicsBone");
  return TYPE_NAME;
}

const char* PhysicsBone::GetMetaName() { return "PhysicsBoneMeta"; }

void PhysicsBone::FillBones(PhysicsParticleTree* tree, ArmatureNode* node,
    int parent_index, float bone_length) {
  // 创建项目
  int index = static_cast<int>(tree->particles.size());
  auto& p = tree->particles.emplace_back(*tree, index);

  // 设置骨骼ID
  p.bone_id = node->bone_id;
  p.node = node;

  // 从t-pose中获得位置
  IVec pos, rot, scale;
  XMMatrixDecompose(&scale, &rot, &pos, node->tpose_mat);

  // 从GameObject中获得位置，将当前位置换算为世界坐标。设置初始的坐标
  IVec world_pos = XMVector3TransformCoord(pos, trans_mat_);
  p.position = world_pos;
  p.prev_position = p.position;

  // 设置父级，并且设置父级的子项目数量
  p.parent_index = parent_index;
  if (parent_index != -1) {
    // 得到父级
    auto& pp = tree->particles[parent_index];
    pp.child_count++;

    // 计算BoneLength
    bone_length += XMVectorGetX(XMVector3Length(pp.position - p.position));
  }

  // 设置骨骼长度
  p.bone_length = bone_length;
  // 设置骨骼最大长度
  tree->bone_total_length = imath::Max(tree->bone_total_length, bone_length);

  // 如果有子项，继续创建
  if (node->children.size() > 0) {
    for (size_t i = 0; i < node->children.size(); i++) {
      FillBones(tree, node->children[i], index, bone_length);
    }
  } else {
    // 如果没有Childen，检查是否要增加尾部骨骼
    if (tree->setting.has_end) {
      AddEndBone(tree, index, bone_length);
    }
  }
}

void PhysicsBone::AddEndBone(
    PhysicsParticleTree* tree, int parent_index, float bone_length) {
  // 创建项目
  int index = static_cast<int>(tree->particles.size());
  auto& p = tree->particles.emplace_back(*tree, index);
  // 获得父级
  auto& parent = tree->particles[parent_index];

  // 这个无论是有值，还是0，都可以直接配置。
  IVec offset_pos = tree->setting.end_offset;
  // 如果是指定长度，需要根据骨骼的方向进行延长，这意味着至少需要两个父级
  if (tree->setting.end_length > F_EPSILON) {
    IVec parent_position = imath::PickTranslate(parent.node->tpose_mat);
    IVec grand_position = imath::PickTranslate(parent.node->parent->tpose_mat);
    IVec dir = XMVector3Normalize(parent_position - grand_position);
    // 得到最终的Offset
    offset_pos += dir * tree->setting.end_length;
    // 这个额外的小位移是不让他变成同轴，否则会出现问题
    offset_pos += XMVectorSet(0.005f, 0.01f, 0.005f, 0.0f);
  }

  // 设置为粒子的属性，以后每次都需要转换
  p.end_offset = offset_pos;

  // 从GameObject中获得位置，将当前位置换算为世界坐标。设置初始的坐标
  offset_pos = vector_to_world(p.end_offset, trans_mat_);
  p.position = parent.position + offset_pos;
  p.prev_position = p.position;

  // 设置父级，并且设置父级的子项目数量
  p.parent_index = parent.index;
  parent.child_count++;

  // 计算BoneLength
  bone_length += XMVectorGetX(XMVector3Length(parent.position - p.position));
  // 设置骨骼长度
  p.bone_length = bone_length;
  // 设置骨骼最大长度
  tree->bone_total_length = imath::Max(tree->bone_total_length, bone_length);

  // 已经没有子级需要创建了。
}

void PhysicsBone::UpdatePhysics(float delta_time) {
  // 设置DeltaTime，避免每次传递参数
  this->delta_time_ = delta_time;

  // 从Transform中获得矩阵
  auto& t = game_object_->GetTransform();
  trans_mat_ = t.mat;
  // 计算逆矩阵，用于将Position还回去
  inv_trans_mat_ = XMMatrixInverse(nullptr, trans_mat_);

  // 得到当前的位置，并计算对象的移动
  object_position_ = XMLoadFloat3(&t.position);
  object_move_ = object_position_ - prev_object_position_;
  object_move_length_ = XMVector3Length(object_move_);
  prev_object_position_ = object_position_;

  // 处理每个分支
  for (auto& e : trees_) {
    // 计算Wave
    if (e->setting.has_wave) {
      // 使用正弦函数生成平滑波形
      float wave_freq = 1.0f / e->setting.wave_time; // 频率 = 1/周期
      e->wave = e->setting.wave * sin(2 * XM_PI * wave_freq * e->wave_tick);
      e->wave_world = vector_to_world(e->wave, trans_mat_);
      e->wave_tick += delta_time_;
    }

    // 进行verlet积分处理
    for (auto& p : e->particles) {
      UpdateParticle(p);
    }
    // 进行其它的修正
    for (auto& p : e->particles) {
      AdjustParticle(p);
    }
    // 将项目变换回骨骼
    for (auto& p : e->particles) {
      ApplyParticle(p);
    }
  }
}

void PhysicsBone::UpdateParticle(PhysicsParticle& p) {
  auto& cfg = p.parent.setting;

  // 检查是尾部骨骼还是正常骨骼
  if (p.bone_id != -1) {
    // 从Bone之中获得矩阵
    auto& bone_mat = animator_->final_mats_[p.bone_id];
    // 与Transform进行计算，得到世界矩阵
    auto world_mat = bone_mat * trans_mat_;
    // 从世界矩阵中提取出位置
    // 为什么叫"rest_pos"，是因为这就是在正常动画下，这个骨骼应该存在的位置。
    p.rest_position = imath::PickTranslate(world_mat);
  } else {
    // 从和父级的关系中，取得当前的位置
    auto offset_pos = vector_to_world(p.end_offset, trans_mat_);
    p.rest_position = p.GetParent().rest_position + offset_pos;
  }

  // 如果是根节点，不做任何变换
  if (p.index == 0) {
    p.position = p.rest_position;
    p.prev_position = p.rest_position;
    return;
  }

  // 计算引力
  auto gravity = p.parent.setting.gravity;
  auto force = gravity * delta_time_ * delta_time_;

  // 使用一个默认的
  float damping = cfg.damping;
  float inert = 0.0f;

  auto rmove = object_move_ * p.inert;

  // 进行verlet积分
  auto velocity = p.rest_position - p.prev_position;
  is_logable_ = XMVectorGetX(XMVector3Length(velocity)) > 0.001f;
  // 对velocity进行修正。我不知道为什么在Unity的插件里面，他的velocity始终是一个非常小的值。
  // 即便拥有巨大的位移，这个值也很小。我只有在这里对速度进行钳制。
  velocity *= cfg.velocity_scale;

  p.prev_position = p.position;
  p.position += velocity * (1.0f - damping) + force;

  // 对波浪进行计算
  if (cfg.has_wave && XMVectorGetX(object_move_length_) > 0.01f) {
    float flip = p.index % 2 == 0 ? 1.0f : -1.0f;
    p.position += p.parent.wave_world * flip;
  }
}

void PhysicsBone::AdjustParticle(PhysicsParticle& p) {
  // 如果是根节点，不做任何变换
  if (p.index == 0) {
    return;
  }

  // 刚度（僵硬度）。这个数值越大，也就意味着抵抗变形的能力越强，就是越不容易变形。
  float stiffness = p.parent.setting.stiffness;
  // 弹性。这个数值越大，意味着能够快速回到原来的位置，就是变形后复原的速度。
  float elasticity = p.parent.setting.elasticity;

  // 在PhysicsBone里面，timeVar是一个很奇怪的值，是当前的deltaTime *
  // 60（更新率）。 在我的电脑上，经常是0.25左右。
  float timeVar = 0.25;

  auto d = p.rest_position - p.position;

  // 计算RestLength长度
  auto& pp = p.parent.particles[p.parent_index];
  float rest_len = imath::len(pp.rest_position - p.rest_position);

  // 得到当前位置和rest位置的差值
  p.position += d * (elasticity * timeVar);

  if (stiffness > 0) {
    d = p.rest_position - p.position;
    float len = imath::len(d);
    float maxlen = rest_len * (1 - stiffness) * 2;
    if (len > maxlen) {
      p.position += d * ((len - maxlen) / len);
    }
  }

  // 保持项目的长度
  IVec dd = pp.position - p.position;
  float len = imath::len(dd);
  if (len > 0) {
    auto diff = len - rest_len;
    auto modify = dd * (diff / len);
    p.position += modify;
  }
}

void PhysicsBone::ApplyParticle(PhysicsParticle& p) {
  // 将position变换回去，设置到骨骼
  if (p.bone_id != -1) {
    IVec bone_pos = coord_to_world(p.position, inv_trans_mat_);
    auto& bone_mat = animator_->final_mats_[p.bone_id];
    imath::SetTranslate(bone_mat, bone_pos);
  }

  // 修正方向，是这样的，在子级中计算父级的旋转
  if (p.index > 0) {
    // 得到父级
    auto& pp = p.parent.particles[p.parent_index];
    auto& parent_mat = animator_->final_mats_[pp.bone_id];

    // 将旋转替换
    auto rot = imath::GetBoneRotationFix(
        parent_mat, pp.rest_position, p.rest_position, pp.position, p.position);
    parent_mat = imath::SetRotation(parent_mat, rot);

    // auto rot = imath::CalculateCorrectedRotation(
    //     parent_mat, bone_mat, pp.node->tpose_mat, p.node->tpose_mat);
    // parent_mat = imath::SetRotation(parent_mat, rot);
  }

  // if (is_logable_) {
  //   KLOG("*%s* %.3f,%.3f,%.3f => %.3f,%.3f,%.3f(W:%.3f,%.3f,%.3f)",
  //       p.node->name.c_str(), VEC_PF(p.rest_position), VEC_PF(bone_pos),
  //       VEC_PF(p.position));
  // }
}

PhysicsParticleTree::PhysicsParticleTree(
    PhysicsBone& _parent, const PhysicsBoneSetting& _setting)
    : parent(_parent)
    , setting(_setting)
    , bone_total_length(0)
    , wave_flip(1.0f)
    , wave_tick(0)
    , wave(VEC0) {}

PhysicsParticle::PhysicsParticle(PhysicsParticleTree& _parent, int _index)
    : parent(_parent), index(_index), bone_id(-1), node(nullptr) {}

PhysicsParticle& PhysicsParticle::GetParent() {
  return parent.particles[parent_index];
}

} // namespace ifire::game